﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using ClieOp.Record;

namespace ClieOp
{
    /// <summary>
    /// The main container for a set of ClieOp batches.
    /// </summary>
    [Serializable]
    public class Bestand
    {
        private const long Duplicaatcode = 1;
        private static readonly CultureInfo Ci = CultureInfo.InvariantCulture;
        private readonly long _aanmaakdatumbestand = Utility.ClieOpDatum();
        private readonly List<Batch> _batches = new List<Batch>();
        private readonly string _inzendersidentificatie;
        private readonly TransactionFileType _transactionFileType;
        private string _bestandsidentificatie;
        private long _bestandssvolgnummer = 1;

        /// <summary>
        /// Create a new ClieOp file.
        /// </summary>
        /// <param name="inzendersidentificatie">Identification assigned by the sender to himself.</param>
        public Bestand(string inzendersidentificatie)
        {
            _inzendersidentificatie = inzendersidentificatie;
            _transactionFileType = TransactionFileType.DirectDebit;
        }

        /// <summary>
        /// Create a new ClieOp file.
        /// </summary>
        /// <param name="inzendersidentificatie">Identification assigned by the sender to himself.</param>
        /// <param name="transactionFileType">The transaction group for this file.
        /// (Note that the transaction group is a property of `Batch', but that it is illegal to mix multiple transaction groups in one `Bestand'.</param>
        public Bestand(string inzendersidentificatie, TransactionFileType transactionFileType)
        {
            _inzendersidentificatie = inzendersidentificatie;
            _transactionFileType = transactionFileType;
        }

        /// <summary>
        /// Identifies the file uniquely per month.
        /// </summary>
        public string Bestandsidentificatie
        {
            get
            {
                if (string.IsNullOrEmpty(_bestandsidentificatie))
                {
                    _bestandsidentificatie = string.Format(Ci, "{0}{1:00}", Utility.ClieOpNum(_aanmaakdatumbestand.ToString(Ci), 6).Substring(0, 2),
                                                           Bestandssvolgnummer);
                }
                return _bestandsidentificatie;
            }
            set { _bestandsidentificatie = value; }
        }

        /// <summary>
        /// Sequence number, incremented from one by one per delivery day.
        /// </summary>
        public long Bestandssvolgnummer
        {
            get { return _bestandssvolgnummer; }
            set { _bestandssvolgnummer = value; }
        }

        /// <summary>
        /// A list of Batches contained in this Bestand.
        /// </summary>
        public IList<Batch> Batches
        {
            get { return _batches; }
        }

        /// <summary>
        /// Add a new batch to the Bestand.
        /// </summary>
        /// <param name="batch">A Batch object.</param>
        /// <exception cref="ArgumentException">Cannot add `Batch'; maximum is 9999.</exception>
        public void AddBatch(Batch batch)
        {
            if (_batches.Count >= 9999)
            {
                throw new ArgumentException("Cannot add `Batch'; maximum is 9999.");
            }
            batch.Batchvolgnummer = _batches.Count + 1;
            batch.TransactionFileType = _transactionFileType;
            _batches.Add(batch);
        }

        /// <summary>
        /// Serialize the Bestand object to a valid ClieOp text document.
        /// </summary>
        /// <returns>The ClieOp file as a string.</returns>
        public new string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(
                new Bestandsvoorlooprecord
                    {
                        Aanmaakdatumbestand = _aanmaakdatumbestand,
                        Bestandsidentificatie = Bestandsidentificatie,
                        Duplicaatcode = Duplicaatcode,
                        Inzendersidentificatie = _inzendersidentificatie
                    }.ToString());
            foreach (Batch batch in Batches)
            {
                sb.Append(batch.ToString());
            }
            sb.Append(new Bestandssluitrecord().ToString());
            return sb.ToString();
        }

        /// <summary>
        /// Write the ClieOp text to a file.
        /// </summary>
        /// <param name="fileName">The full path and file information for the new file.</param>
        public void WriteToFile(string fileName)
        {
            var sw = new StreamWriter(fileName);
            sw.Write(ToString());
            sw.Close();
        }

        /// <summary>
        /// Parses a complete Bestand from the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>A Bestand object.</returns>
        public static Bestand Parse(Stream stream)
        {
            using (var reader = new ClieOpReader(stream))
            {
                return Parse(reader);
            }
        }

        /// <summary>
        /// Parses a complete Bestand from the specified file.
        /// </summary>
        /// <param name="path">The path to a file.</param>
        /// <returns>A Bestand object.</returns>
        public static Bestand Parse(string path)
        {
            using (var reader = new ClieOpReader(path))
            {
                return Parse(reader);
            }
        }

        /// <summary>
        /// Parses a complete Bestand from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>A Bestand object.</returns>
        internal static Bestand Parse(ClieOpReader reader)
        {
            Bestand bestand = null;
            if (reader.CurrentRecord != null && reader.CurrentRecord.RecordType == RecordType.Bestandsvoorlooprecord)
            {
                var record = (Bestandsvoorlooprecord)reader.CurrentRecord;
                string inzendersidentificatie = record.Inzendersidentificatie;
                string bestandsidentificatie = record.Bestandsidentificatie;
                reader.MoveToNext();
                var batches = new List<Batch>();
                Batch batch = Batch.Parse(reader);
                while (batch != null)
                {
                    batches.Add(batch);
                    batch = Batch.Parse(reader);
                }
                bestand = batches.Count > 0 ? new Bestand(inzendersidentificatie, batches[0].TransactionFileType) : new Bestand(inzendersidentificatie);
                bestand.Bestandsidentificatie = bestandsidentificatie;
                bestand.Bestandssvolgnummer = long.Parse(bestandsidentificatie.Substring(2));
                foreach (Batch batch1 in batches)
                {
                    bestand.AddBatch(batch1);
                }
                if (reader.CurrentRecord == null || reader.CurrentRecord.RecordType != RecordType.Bestandssluitrecord)
                {
                    throw new Exception("Bestandssluitrecord missing.");
                }
            }
            return bestand;
        }
    }
}